package fun.ticsmyc.rpc.client.proxy;

import fun.ticsmyc.rpc.client.transport.RpcClient;
import fun.ticsmyc.rpc.common.entity.RpcRequest;
import fun.ticsmyc.rpc.common.entity.TRPCServiceProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;
import java.util.UUID;

/**
 * 使用动态代理，根据接口+方法声明，生成实现类
 * 生成的实现类在调用方法时，调用的是本类的invoke方法
 * @author Ticsmyc
 * @date 2020-10-23 11:42
 */
public class ServiceProxy implements InvocationHandler {

    private static final Logger logger= LoggerFactory.getLogger(ServiceProxy.class);

    private RpcClient rpcClient;
    private String group;

    public ServiceProxy( RpcClient rpcClient,String group) {
        this.rpcClient = rpcClient;
        this.group = group;
    }

    public  <T> T getProxy(Class<T> clazz){
        return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{clazz},this);
    }

    /**
     * 当代理对象调用方法时触发
     * @param proxy  代理对象
     * @param method 被调用的方法
     * @param args   方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)  {
        try{
            //如果执行的是Object类的方法或者代理对象的方法，则不执行下面的rpc逻辑
            //问题来源： 当使用idea进行调试时，会自动的调用一堆toString方法，也就是说
            //          会发送一堆rpc请求，要调java.lang.Object_t ，如果不处理，自然是错误请求了
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        logger.debug("调用方法:{}#{}",method.getDeclaringClass().getName(),method.getName());
        //根据接口信息，封装请求数据
        RpcRequest rpcRequest = new RpcRequest();
        rpcRequest.setInterfaceName(method.getDeclaringClass().getName());
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParamTypes(method.getParameterTypes());
        rpcRequest.setParameters(args);
        rpcRequest.setHeartBeat(false);
        rpcRequest.setRequestId(UUID.randomUUID().toString());
        rpcRequest.setGroup(group);
        logger.debug("服务代理发送rpc请求："+rpcRequest);

        TRPCServiceProperties trpcServiceProperties = TRPCServiceProperties.builder().group(rpcRequest.getGroup()).serviceName(rpcRequest.getInterfaceName()).build();
        //将请求数据发送到服务端，获取请求结果
        Object response= rpcClient.sendRequest(rpcRequest,trpcServiceProperties);
        //将请求结果返回
        return response;
    }
    /**
     * Backport of java.lang.reflect.Method#isDefault()
     */
    private boolean isDefaultMethod(Method method) {
        return (method.getModifiers()
                & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
                && method.getDeclaringClass().isInterface();
    }

    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
            throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor
                .newInstance(declaringClass,
                        MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
                .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }
}
